#include<cerrno>
#include"http.hpp"
#include<stdexcept>

using namespace std;

Url::Url(const string& url) {
  scheme="http";  port = 0;
  string::const_iterator po, pe, ps, pae, pbe;
  po = url.begin(); pe = url.end();
  ps = find_if(po, pe, not1(ptr_fun((int(*)(int))isalpha)));
  if (ps!=pe && *ps == ':') {
    scheme = string(po, ps);
    po = ps + 1;
    if (ps+2<pe && *(ps+1) == '/' && *(ps+2)=='/') po+=2;
  }
  for(ps = po; ps!=pe; ++ps) {
    if (*ps=='@'||*ps =='/'||*ps=='?')
      break;
  }
  if (ps != pe && *ps == '@') {
    auth = string(po, ps);
    po = ps + 1;
  }
  pae = find(po, pe, '?');
  pbe = find(po, pae, '/');
  ps  = find(po, pbe, ':');
  host = string(po, ps);
  if (ps < pbe) {
    ++ps;
    while (ps < pbe) {
      if (isdigit(*ps))
        port = port*10 + (*ps) - '0';
      else break;
      ++ps;
    }
  } else {
    port = default_port();
  }
  path = string(pbe, pae);
  if (pae < pe) {
    po = pae + 1;
    while (po < pe) {
      pae = find(po, pe, '&');
      pbe = find(po, pae, '=');
      if (po < pbe) {
        if (pbe+1 < pae)
          params.push_back(make_pair(string(po, pbe), string(pbe+1, pae)));
        else
          params.push_back(make_pair(string(po, pbe), string()));
      }
      if (pae < pe)
        po = pae + 1;
      else po = pe;
    }
  }
}

string Url::get_query_str() const
{
  string str = path;
  if (params.size()) {
    for (unsigned i=0; i<params.size(); ++i) {
      if (i==0) str+='?';
      else str+='&';
      str+=params[i].first+'='+params[i].second;
    }
  }
  if (str.empty()) str="/";
  return str;
}

int Url::scheme_port(const string&sche)
{
  if (sche =="http") return 80;
  if (sche =="ftp") return 21;
  if (sche =="file") return 0;
  return 80;//
}

int Url::default_port()const
{
  return scheme_port(scheme);
}

string Url::to_string() const
{
  ostringstream os;
  if (scheme.size()) os<<scheme<<"://";
  if (auth.size()) os<<auth<<"@";
  os<<host;
  if (port && port!=default_port()) os<<":"<<port;
  os<<get_query_str();
  return os.str();
}

ostream& Url::out_to(ostream &out) const {
  out<<"URL:{"<<"scheme:"<<scheme<<" auth:"<<auth<<" host:"<<host<<" port:"<<port<<
    " path:"<<path<<" params:";
  cout<<"(";
  for (unsigned i=0; i<params.size(); ++i) {
    if (i>0) cout<<", ";
    cout<<params[i].first<<":"<<params[i].second;
  }
  cout<<")";
  out<<"}";
  return out;
}

bool Proxy::empty() const {
  return (purl.host.empty() || purl.port == 0);
}

void HttpHeader::clear() {
  headers.clear();
}

void HttpHeader::merge(const HttpHeader &o) {
  for (unsigned i=0; i<o.headers.size(); ++i) {
    add(o.headers[i].first, o.headers[i].second);
  }
}

void HttpHeader::add(const string& k, const string& v) {
  string value= v;
  if (v.size() && v[0] == ' ') {
    value.erase(value.begin());
  }
  if (value.size() && value[value.size()-1] == '\r') {
    value.erase(value.end()-1);
  }
  for (unsigned i=0; i<headers.size(); ++i) {
    if (headers[i].first == k) {
      headers[i] = make_pair(k, value);
      return ;
    }
  }
  headers.push_back(make_pair(k,value));
}

void HttpHeader::remove(const string&k) {
  for (unsigned i=0; i<headers.size(); ++i) {
    if (headers[i].first ==k) {
      headers.erase(headers.begin()+i);
      return ;
    }
  }
}

string HttpHeader::get(const string& k)const {
  for (unsigned i=0; i<headers.size(); ++i) {
    if (headers[i].first == k) return headers[i].second;
  }
  return string();
}

// >0 still in//0 finish //-1 not know //<-1 error
int HttpHeader::fetch_from(string& str) {
  string::iterator po, pe, ps, pae, pbe;
  po = str.begin(); pe = str.end();
  while (po < pe) {
    pae = find(po, pe, '\n');
    if (pae == pe) {// not finish
      str = string(po, pe);
      return 1;//still in
    }
    pbe = pae;
    if (pbe > po && *(pbe-1) =='\r') --pbe;
    if (po == pbe) {//empty line?
      str =  string(pae+1, pe);
      return 0;
    }
    ps = find(po, pbe, ':');
    if (ps==pbe) {//error:non header format?
      return -2;
    }
    add(string(po, ps), string(ps + 1, pbe));
    po = pae + 1;//here force pae < pe
  }
  str = string(po, pe);
  return 1;//still in
}

string HttpHeader::to_string(const HttpHeader*hd) const {
  string s;
  for (unsigned i=0; i<headers.size(); ++i) {
    s+=headers[i].first+": "+headers[i].second+'\n';
  }
  if (hd) {
    string tmp;
    for (unsigned i=0; i<hd->headers.size(); ++i) {
      tmp = get(hd->headers[i].first);
      if (tmp.empty()) {
        s+=hd->headers[i].first+": "+hd->headers[i].second+'\n';
      }
    }
  }
  return s;
}

string HttpHeader::to_contstr(const HttpHeader*hd) const {
  string s;
  int first=1;
  for (unsigned i=0; i<headers.size(); ++i) {
    if (!first) s+='&';
    s+=headers[i].first+"="+headers[i].second;
    first = 0;
  }
  if (hd) {
    string tmp;
    for (unsigned i=0; i<hd->headers.size(); ++i) {
      tmp = get(hd->headers[i].first);
      if (tmp.empty()) {
        if (!first) s+='&';
        s+=hd->headers[i].first+"="+hd->headers[i].second;
        first=0;
      }
    }
  }
  return s;
}

string HttpHeader::to_dbgstring() const {
  string s;
  for (unsigned i=0; i<headers.size(); ++i) {
    s+="key|"+headers[i].first+"|: value|"+headers[i].second+"|\n";
  }
  return s;
}

HttpRequest::HttpRequest(const Url& u, Method m) : method(m), url(u) {
  add_header("Host", u.host);
}

void HttpRequest::add_header(const string& k, const string& v) {
  header.add(k, v);
}


string HttpRequest::get_host() const {
  return url.host;
}

int HttpRequest::get_port() const {
  return url.port;
}

string HttpRequest::build_com(int full) const {
  return string("GET ") +
    (full ? url.to_string() : url.get_query_str())
    + " HTTP/1.1\n";
}

string HttpRequest::build_header(const HttpHeader* oh) const {
  return header.to_string(oh);
}

HttpRequestPost::HttpRequestPost(const Url& u):HttpRequest(u, Method_POST) {
  content_type = "application/x-www-form-urlencoded";
}

void HttpRequestPost::add_cont(const string&k, const string&v) {
  cont.add(k, v);
}

void HttpRequestPost::add_cont(const string&ed) {
  string::const_iterator po =ed.begin(), pe = ed.end(), ps, pae;
  while(po < pe) {
    pae = find(po, pe, '&');
    ps = find(po, pae, '=');
    if (po < ps && ps < pae) {
      cont.add(string(po, ps), string(ps+1, pae));
    }
    if (pae < pe)
      po = pae + 1;
    else
      po = pe;
  }
}

string HttpRequestPost::build_cont(const HttpHeader* oh)const {
  return cont.to_contstr(oh);
}

string HttpRequestPost::build_com(int full) const {
  return string("POST ") +
    (full ? url.to_string() : url.get_query_str())
    + " HTTP/1.1\n";
}

int HttpResponse::fetch_from(TcpClient& tcpclient, string &str, bool start)
{
  if (str.empty())
    tcpclient.read_all(str);
  int r = fetch_from(str, start);
  int rr=1;
  while (r>0 && rr>0) {
    rr = tcpclient.read_all(str);
    r = fetch_from(str);
  }
  return r;
}

//>0 should again// 0 done//== -1 error//<-1 not know
int HttpResponse::fetch_from(string &str, bool start) {
  if (start) {
    fstatus = 0;
    contentlen = 0;
    ischunked = 0;
    lastchunked = -1;
    ret = 0;
    header.clear(); content.clear(); statusline.clear();
  }
  string::iterator po, pe, pae, pbe, ps;
  po = str.begin();
  pe = str.end();
  if (fstatus ==0) {
    pae = find(po, pe, '\n');
    if (pae == pe) return 1;//again
    if (str.substr(0, 4) == "HTTP") {
      istringstream is(string(po, pae));
      string prot;
      is>>prot>>ret>>statusline;
      //cerr<<"get statusline="<<statusline<<" ret="<<ret<<" prot="<<prot<<endl;
      if (!ret) {
        statusline.clear();
        fstatus = 10;
      } else {
        str = string(pae+1, pe);
        fstatus = 1;
      }
    } else {
      fstatus = 10;
    }
  }
  if (fstatus == 1) {// get header
    //cerr<<"now get header from:"<<str.substr(0, 512)<<"...\n"<<endl;
    int r = header.fetch_from(str);
    if (r < -1) {
      fstatus = 10; //non header!
    } else if (r == 0) {//get all header
      fstatus = 10;
      contentlen = atoi(header.get("Content-Length").c_str());
      ischunked = (header.get("Transfer-Encoding") == "chunked");
      //cerr<<"Content-Length="<<header.get("Content-Length")<<" contentlen="<<contentlen<<endl;
    }
  }
  if (fstatus == 10) {//get content
    if (ischunked) {
      if (lastchunked==0) {
        return header.fetch_from(str);
      }
      po = str.begin(); pe = str.end();
      while (po < pe) {
        pae = find(po, pe, '\n');
        if (pae == pe) return 1;//
        int len = strtol(&(*po), 0, 16);
        ps = pae + 1;
        if (len==0) {
          str = string(ps, pe);
          int r = header.fetch_from(str);
          lastchunked = len;
          //cerr<<"chunked 0"<<endl;
          return r;
        }
        if (pe - ps >= len ) {
          pbe = ps + len;
          pae = find(pbe, pe, '\n');
          if (pae == pe) break;
          lastchunked = len;
          //cerr<<"chunked get:"<<(pe-pbe)<<endl;
          content += string(ps, pbe);
        } else break;
        if (pae < pe)
          po = pae + 1;
        else
          po = pe;
      }
      str = string(po, pe);
      return 1;
    } else {
      content+=str;
      str.clear();
      if((unsigned)contentlen > content.size())
        return contentlen - content.size();
    }
  }
  return -2;//em, I do not know if it get the end
}

bool HttpResponse::is_success() {
  if (ret >= 200 && ret < 300) {
    return true;
  }
  return false;
}

string HttpResponse::to_string() {
  return string(is_success() ? "Response success\n" : "false\n") + "\nHeader:\n" +
    header.to_dbgstring()+"\nData:\n"+content;
}


HttpAgent::HttpAgent(int to): tcpclient(to), timeout(to) {
  header.add("Keep-Alive", "300");
  header.add("Connection", "keep-alive");
  header.add("Accept", "text/*");
  header.add("UserAgent", "Sheos/0.001");
}

void HttpAgent::set_proxy(const string& u) {
  proxy = Proxy(u);
  if (proxy.empty()) {
    header.remove("Proxy-Connection");
    header.add("Connection", "keep-alive");
  } else {
    header.remove("Connection");
    header.add("Proxy-Connection", "keep-alive");
  }
}

bool HttpAgent::has_proxy() const {
  return !proxy.empty();
}

int HttpAgent::request(HttpResponse& resp, const HttpRequest& req) {
  int r;
  string rws, orws;
  int uproxy = has_proxy();
  if (!uproxy) {
    TcpHost host(req.get_host(), req.get_port());
    tcpclient.connect(host);
  } else {
    TcpHost host(proxy.purl.host, proxy.purl.port);
    tcpclient.connect(host);
  }
  if (req.method==HttpRequest::Method_GET)
    rws = req.build_com(uproxy) + req.build_header(&header)+"\n";
  else {//POST
    char buf[16];
    const HttpRequestPost &reqp=*(reinterpret_cast<const HttpRequestPost*>(&req));
    orws = reqp.build_cont();
    snprintf(buf, sizeof(buf), "%zd", orws.size());
    rws = reqp.build_com(uproxy) + reqp.build_header(&header)
      +"Content-Type: " + reqp.content_type + "\n"
      +"Content-Length: " + buf +"\n"
      +"\n"+orws;
  }
  orws=rws;
  //cerr<<"Write to server:\n"<<rws<<"ENDWRITE"<<endl;
  errno=0;
  r = tcpclient.write_all(rws);
  if (r!=0) {
    if (errno == EAGAIN || errno ==EWOULDBLOCK || errno == EINTR) {
      r = tcpclient.write_all(rws);//again
    } else if(errno == ECONNRESET|| errno==EPIPE || errno == ENOTCONN) {
      rws = orws;
      r = tcpclient.write_all(rws);//try again
    }
  }
  if (r > 0 ) {
    tcpclient.close();
    //cerr<<"write all failed!"<<endl;
    return -1;
  }
  rws.clear();//just again for robust
  r = fetch(resp, rws, true);//r = resp.fetch_from(tcpclient, rws, timeout, true);
  if (!rws.empty()) resp.content += rws;
  //cerr<<"after all fetch, last is:\n"<<rws<<"ENDDATA\n"<<endl;
  string kc="Connection";
  if (uproxy) kc = "Proxy-"+kc;
  if (resp.header.get(kc) == "close") {
    tcpclient.closewrite();
  }
  return r;
}

int HttpAgent::fetch(HttpResponse &resp, string& str, bool fstart) {
  return resp.fetch_from(tcpclient, str, fstart);
}
